package plus.decl;

import java.util.*;

import plus.DynamicExtent;
import plus.FEnv;
import plus.TEnv;
import plus.Uni;
import plus.run.Label;
import plus.stmt.Statement;

public class Subroutine {
	//public static Subroutine current;
	private DeclBucket db;
	final Statement stmt;
	private DynamicExtent dynex;
	public Subroutine(DeclBucket decls, Statement stmt) {
		assert decls != null;
		assert stmt != null;
		db = decls;
		this.stmt = stmt;
	}
	public void threadScope(DynamicExtent dynex) {
		// Formal parameters are already introduced in the outer environment.
		this.dynex = dynex;

		// So that means the VARIABLE scope has already been entered, with
		// this subroutine's activation record as the host dynamic scope.

		// NOTE: the temptation is to use the Subroutine as THE function-scoping
		// object. That approach fails to work for "built-in" functions, and
		// also essentially precludes the "using" construct.
		TEnv.enterSub();
		for (D_Record d:db.records()) d.declare();
		for (D_TypeDef d:db.typeDefs()) d.declare();
		for (D_TypeDef d:db.typeDefs()) d.define();
		for (D_Record d:db.records()) d.define();
		for (D_Var vd:db.varDecls()) vd.introduce();
		// TODO handle givens (before processing nested functions.)
		// (Longer term, might need to factor givens out into another scope-like object for modules' sake? Or might combine with type-scope?)
		FEnv fe = new FEnv();
		Uni.pushFE(fe);
		for (D_Function fd:db.fnDefs()) fe.enter(fd.sym);
		for (D_Function fd:db.fnDefs()) fd.threadScope(dynex.lexLevel+1);

		// Go through the local code, hooking things up as needed.
		//current = this; // but first make sure return statements can find what they need.
		stmt.threadScope();
		Uni.popFE();
		TEnv.exit();

		// The surrounding D_Function (or Program) will exit the corresponding VARIABLE SCOPE.

	}
	public final Label entryPoint = new Label();

	private static List<Subroutine> workQueue = new LinkedList<Subroutine>();
	private boolean already_scheduled = false;
	public void scheduleCompile() {
		if (!already_scheduled) {
			workQueue.add(this);
			this.already_scheduled = true;
		}
	}
	public static void compileAll() {
		while (!workQueue.isEmpty()) workQueue.remove(0).compile();
	}
	private void compile() {
		entryPoint.post();
		dynex.compileEnter();
		stmt.compile();
		dynex.compileExit();
		db = null;
	}
}
